package com.zhangfb95.crosschain.sdk;

import com.zhangfb95.crosschain.infra.exception.BusinessException;
import com.zhangfb95.crosschain.infra.model.Result;
import com.zhangfb95.crosschain.infra.util.tls.client.ReactorNettyWebClientBuilder;
import com.zhangfb95.crosschain.infra.util.tls.client.WebClientSslProperties;
import com.zhangfb95.crosschain.protocol.rpc.RpcAuthHandler;
import com.zhangfb95.crosschain.sdk.infra.config.properties.CrossChainServerProperties;
import com.zhangfb95.crosschain.sdk.infra.data.api.server.RpcChainAccountApi;
import com.zhangfb95.crosschain.sdk.infra.data.api.server.RpcChainApi;
import com.zhangfb95.crosschain.sdk.infra.data.api.server.RpcUserApi;
import com.zhangfb95.crosschain.sdk.infra.model.api.request.server.rpcchain.RpcGetBlockByNumberApiReq;
import com.zhangfb95.crosschain.sdk.infra.model.api.request.server.rpcchain.RpcGetHeightApiReq;
import com.zhangfb95.crosschain.sdk.infra.model.api.request.server.rpcchain.RpcGetTxByIdApiReq;
import com.zhangfb95.crosschain.sdk.infra.model.api.request.server.rpcchain.RpcQueryApiReq;
import com.zhangfb95.crosschain.sdk.infra.model.api.request.server.rpcchain.RpcSendApiReq;
import com.zhangfb95.crosschain.sdk.infra.model.api.request.server.rpcchainaccount.RpcChainAccountAddApiReq;
import com.zhangfb95.crosschain.sdk.infra.model.api.request.server.rpcchainaccount.RpcChainAccountAddPropertiesApiReq;
import com.zhangfb95.crosschain.sdk.infra.model.api.request.server.rpcchainaccount.RpcChainAccountListApiReq;
import com.zhangfb95.crosschain.sdk.infra.model.api.request.server.rpcchainaccount.RpcChainAccountRemoveApiReq;
import com.zhangfb95.crosschain.sdk.infra.model.api.request.server.rpcchainaccount.RpcChainAccountRemovePropertiesApiReq;
import com.zhangfb95.crosschain.sdk.infra.model.api.request.server.rpcuser.RpcUserAddApiReq;
import com.zhangfb95.crosschain.sdk.infra.model.api.request.server.rpcuser.RpcUserLoginApiReq;
import com.zhangfb95.crosschain.sdk.infra.model.api.request.server.rpcuser.RpcUserUpdatePasswordApiReq;
import com.zhangfb95.crosschain.sdk.infra.model.api.request.server.rpcuser.RpcUserUpdateSecretApiReq;
import com.zhangfb95.crosschain.sdk.infra.model.api.request.server.rpcuser.RpcUserUpdateUsernameApiReq;
import com.zhangfb95.crosschain.sdk.infra.model.api.response.server.rpcchain.RpcGetBlockByNumberApiRes;
import com.zhangfb95.crosschain.sdk.infra.model.api.response.server.rpcchain.RpcGetHeightApiRes;
import com.zhangfb95.crosschain.sdk.infra.model.api.response.server.rpcchain.RpcGetTxByIdApiRes;
import com.zhangfb95.crosschain.sdk.infra.model.api.response.server.rpcchain.RpcListChainNameApiRes;
import com.zhangfb95.crosschain.sdk.infra.model.api.response.server.rpcchain.RpcListChainPathApiRes;
import com.zhangfb95.crosschain.sdk.infra.model.api.response.server.rpcchain.RpcQueryApiRes;
import com.zhangfb95.crosschain.sdk.infra.model.api.response.server.rpcchain.RpcSendApiRes;
import com.zhangfb95.crosschain.sdk.infra.model.api.response.server.rpcchainaccount.RpcChainAccountListApiRes;
import com.zhangfb95.crosschain.sdk.infra.model.api.response.server.rpcuser.RpcUserLoginApiRes;
import com.zhangfb95.crosschain.sdk.infra.model.service.dto.UserInfo;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.reactive.function.client.support.WebClientAdapter;
import org.springframework.web.service.invoker.HttpServiceProxyFactory;

/**
 * @author zhangfubing
 * @since 2023/12/14
 */
public class CrossChainClientImpl implements CrossChainClient {

    private final RpcChainApi rpcChainApi;
    private final RpcUserApi rpcUserApi;
    private final RpcChainAccountApi rpcChainAccountApi;
    private UserInfo userInfo;

    public CrossChainClientImpl(CrossChainServerProperties properties) {
        WebClientSslProperties sslProperties = new WebClientSslProperties()
                .setServerUrl(properties.getServerUrl())
                .setSslType(properties.getSslType())
                .setCaCert(properties.getCaCert())
                .setSslKey(properties.getSslKey())
                .setSslCert(properties.getSslCert());
        WebClient webClient = ReactorNettyWebClientBuilder.build(sslProperties);
        HttpServiceProxyFactory httpServiceProxyFactory = HttpServiceProxyFactory
                .builder(WebClientAdapter.forClient(webClient))
                .build();
        this.rpcChainApi = httpServiceProxyFactory.createClient(RpcChainApi.class);
        this.rpcUserApi = httpServiceProxyFactory.createClient(RpcUserApi.class);
        this.rpcChainAccountApi = httpServiceProxyFactory.createClient(RpcChainAccountApi.class);
    }

    @Override
    public Result<Void> add(String username, String decryptedPassword) {
        return rpcUserApi.add(new RpcUserAddApiReq().setUsername(username).setDecryptedPassword(decryptedPassword));
    }

    @Override
    public Result<RpcUserLoginApiRes> login(String username, String decryptedPassword) {
        RpcUserLoginApiReq req = new RpcUserLoginApiReq()
                .setUsername(username)
                .setDecryptedPassword(decryptedPassword);
        Result<RpcUserLoginApiRes> loginResult = rpcUserApi.login(req);

        if (loginResult.isSuccess()) {
            RpcUserLoginApiRes loginApiRes = loginResult.fallback().getData();
            this.userInfo = new UserInfo(String.valueOf(loginApiRes.getId()),
                    username,
                    loginApiRes.getSecret());
        }

        return loginResult;
    }

    @Override
    public UserInfo getLoginedUserInfo() {
        return this.userInfo;
    }

    @Override
    public UserInfo logout() {
        UserInfo currentUserInfo = this.userInfo;
        this.userInfo = null;
        return currentUserInfo;
    }

    @Override
    public Result<Void> updateUsername(RpcUserUpdateUsernameApiReq req) {
        try {
            checkIsLogin();
        } catch (BusinessException e) {
            return Result.fail(e.getCode(), e.getMessage());
        }

        RpcAuthHandler rpcAuthHandler = new RpcAuthHandler(this.userInfo.getUserId());
        Result<Void> result = rpcUserApi.updateUsername(rpcAuthHandler.getUserId(),
                rpcAuthHandler.getNonce(),
                rpcAuthHandler.getTimestamp(),
                rpcAuthHandler.genAuthorization(this.userInfo.getUserSecret()),
                req);
        if (result.isSuccess()) {
            this.userInfo.setUsername(req.getUsername());
        }
        return result;
    }

    @Override
    public Result<Void> updatePassword(RpcUserUpdatePasswordApiReq req) {
        try {
            checkIsLogin();
        } catch (BusinessException e) {
            return Result.fail(e.getCode(), e.getMessage());
        }

        RpcAuthHandler rpcAuthHandler = new RpcAuthHandler(this.userInfo.getUserId());
        return rpcUserApi.updatePassword(rpcAuthHandler.getUserId(),
                rpcAuthHandler.getNonce(),
                rpcAuthHandler.getTimestamp(),
                rpcAuthHandler.genAuthorization(this.userInfo.getUserSecret()),
                req);
    }

    @Override
    public Result<Void> updateSecret(RpcUserUpdateSecretApiReq req) {
        try {
            checkIsLogin();
        } catch (BusinessException e) {
            return Result.fail(e.getCode(), e.getMessage());
        }

        RpcAuthHandler rpcAuthHandler = new RpcAuthHandler(this.userInfo.getUserId());
        Result<Void> result = rpcUserApi.updateSecret(rpcAuthHandler.getUserId(),
                rpcAuthHandler.getNonce(),
                rpcAuthHandler.getTimestamp(),
                rpcAuthHandler.genAuthorization(this.userInfo.getUserSecret()),
                req);
        if (result.isSuccess()) {
            this.userInfo.setUserSecret(req.getSecret());
        }
        return result;
    }

    @Override
    public Result<Void> addChainAccount(RpcChainAccountAddApiReq req) {
        try {
            checkIsLogin();
        } catch (BusinessException e) {
            return Result.fail(e.getCode(), e.getMessage());
        }

        RpcAuthHandler rpcAuthHandler = new RpcAuthHandler(this.userInfo.getUserId());
        return rpcChainAccountApi.add(rpcAuthHandler.getUserId(),
                rpcAuthHandler.getNonce(),
                rpcAuthHandler.getTimestamp(),
                rpcAuthHandler.genAuthorization(this.userInfo.getUserSecret()),
                req);
    }

    @Override
    public Result<Void> removeChainAccount(RpcChainAccountRemoveApiReq req) {
        try {
            checkIsLogin();
        } catch (BusinessException e) {
            return Result.fail(e.getCode(), e.getMessage());
        }

        RpcAuthHandler rpcAuthHandler = new RpcAuthHandler(this.userInfo.getUserId());
        return rpcChainAccountApi.remove(rpcAuthHandler.getUserId(),
                rpcAuthHandler.getNonce(),
                rpcAuthHandler.getTimestamp(),
                rpcAuthHandler.genAuthorization(this.userInfo.getUserSecret()),
                req);
    }

    @Override
    public Result<RpcChainAccountListApiRes> listChainAccount(RpcChainAccountListApiReq req) {
        try {
            checkIsLogin();
        } catch (BusinessException e) {
            return Result.fail(e.getCode(), e.getMessage());
        }

        RpcAuthHandler rpcAuthHandler = new RpcAuthHandler(this.userInfo.getUserId());
        return rpcChainAccountApi.list(rpcAuthHandler.getUserId(),
                rpcAuthHandler.getNonce(),
                rpcAuthHandler.getTimestamp(),
                rpcAuthHandler.genAuthorization(this.userInfo.getUserSecret()),
                req);
    }

    @Override
    public Result<Void> addChainAccountProperties(RpcChainAccountAddPropertiesApiReq req) {
        try {
            checkIsLogin();
        } catch (BusinessException e) {
            return Result.fail(e.getCode(), e.getMessage());
        }

        RpcAuthHandler rpcAuthHandler = new RpcAuthHandler(this.userInfo.getUserId());
        return rpcChainAccountApi.addProperties(rpcAuthHandler.getUserId(),
                rpcAuthHandler.getNonce(),
                rpcAuthHandler.getTimestamp(),
                rpcAuthHandler.genAuthorization(this.userInfo.getUserSecret()),
                req);
    }

    @Override
    public Result<Void> removeChainAccountProperties(RpcChainAccountRemovePropertiesApiReq req) {
        try {
            checkIsLogin();
        } catch (BusinessException e) {
            return Result.fail(e.getCode(), e.getMessage());
        }

        RpcAuthHandler rpcAuthHandler = new RpcAuthHandler(this.userInfo.getUserId());
        return rpcChainAccountApi.removeProperties(rpcAuthHandler.getUserId(),
                rpcAuthHandler.getNonce(),
                rpcAuthHandler.getTimestamp(),
                rpcAuthHandler.genAuthorization(this.userInfo.getUserSecret()),
                req);
    }

    @Override
    public Result<RpcListChainNameApiRes> listChainName() {
        try {
            checkIsLogin();
        } catch (BusinessException e) {
            return Result.fail(e.getCode(), e.getMessage());
        }

        RpcAuthHandler rpcAuthHandler = new RpcAuthHandler(this.userInfo.getUserId());
        return rpcChainApi.listChainName(rpcAuthHandler.getUserId(),
                rpcAuthHandler.getNonce(),
                rpcAuthHandler.getTimestamp(),
                rpcAuthHandler.genAuthorization(this.userInfo.getUserSecret()));
    }

    @Override
    public Result<RpcListChainPathApiRes> listChainPath() {
        try {
            checkIsLogin();
        } catch (BusinessException e) {
            return Result.fail(e.getCode(), e.getMessage());
        }

        RpcAuthHandler rpcAuthHandler = new RpcAuthHandler(this.userInfo.getUserId());
        return rpcChainApi.listChainPath(rpcAuthHandler.getUserId(),
                rpcAuthHandler.getNonce(),
                rpcAuthHandler.getTimestamp(),
                rpcAuthHandler.genAuthorization(this.userInfo.getUserSecret()));
    }

    @Override
    public Result<RpcGetHeightApiRes> getHeight(String chainPath) {
        try {
            checkIsLogin();
        } catch (BusinessException e) {
            return Result.fail(e.getCode(), e.getMessage());
        }

        RpcAuthHandler rpcAuthHandler = new RpcAuthHandler(this.userInfo.getUserId());
        return rpcChainApi.getHeight(rpcAuthHandler.getUserId(),
                rpcAuthHandler.getNonce(),
                rpcAuthHandler.getTimestamp(),
                rpcAuthHandler.genAuthorization(this.userInfo.getUserSecret()),
                new RpcGetHeightApiReq().setChainPath(chainPath));
    }

    @Override
    public Result<RpcGetBlockByNumberApiRes> getBlockByNumber(String chainPath, Long blockNumber) {
        try {
            checkIsLogin();
        } catch (BusinessException e) {
            return Result.fail(e.getCode(), e.getMessage());
        }

        RpcAuthHandler rpcAuthHandler = new RpcAuthHandler(this.userInfo.getUserId());
        return rpcChainApi.getBlockByNumber(rpcAuthHandler.getUserId(),
                rpcAuthHandler.getNonce(),
                rpcAuthHandler.getTimestamp(),
                rpcAuthHandler.genAuthorization(this.userInfo.getUserSecret()),
                new RpcGetBlockByNumberApiReq().setChainPath(chainPath).setBlockNumber(blockNumber));
    }

    @Override
    public Result<RpcGetTxByIdApiRes> getTxById(String chainPath, String txId) {
        try {
            checkIsLogin();
        } catch (BusinessException e) {
            return Result.fail(e.getCode(), e.getMessage());
        }

        RpcAuthHandler rpcAuthHandler = new RpcAuthHandler(this.userInfo.getUserId());
        return rpcChainApi.getTxById(rpcAuthHandler.getUserId(),
                rpcAuthHandler.getNonce(),
                rpcAuthHandler.getTimestamp(),
                rpcAuthHandler.genAuthorization(this.userInfo.getUserSecret()),
                new RpcGetTxByIdApiReq().setChainPath(chainPath).setTxId(txId));
    }

    @Override
    public Result<RpcQueryApiRes> query(RpcQueryApiReq req) {
        try {
            checkIsLogin();
        } catch (BusinessException e) {
            return Result.fail(e.getCode(), e.getMessage());
        }

        RpcAuthHandler rpcAuthHandler = new RpcAuthHandler(this.userInfo.getUserId());
        return rpcChainApi.query(rpcAuthHandler.getUserId(),
                rpcAuthHandler.getNonce(),
                rpcAuthHandler.getTimestamp(),
                rpcAuthHandler.genAuthorization(this.userInfo.getUserSecret()),
                req);
    }

    @Override
    public Result<RpcSendApiRes> send(RpcSendApiReq req) {
        try {
            checkIsLogin();
        } catch (BusinessException e) {
            return Result.fail(e.getCode(), e.getMessage());
        }

        RpcAuthHandler rpcAuthHandler = new RpcAuthHandler(this.userInfo.getUserId());
        return rpcChainApi.send(rpcAuthHandler.getUserId(),
                rpcAuthHandler.getNonce(),
                rpcAuthHandler.getTimestamp(),
                rpcAuthHandler.genAuthorization(this.userInfo.getUserSecret()),
                req);
    }

    private void checkIsLogin() {
        if (this.userInfo == null) {
            throw new BusinessException("user not login");
        }
    }
}
